const { transform } = require("../ot/Transform"); // 引入 transform 函数，用于转换操作对象

// 客户端类，用于模拟客户端的行为
class Client {
  constructor(name) {
    this.name = name; // 客户端的名称
    this.version = 0; // 当前客户端的版本号
    this.buffer = []; // 用于存储本地的操作
    this.doc = ""; // 用于存储当前客户端的文档内容
  }

  /**
   * 本地执行 op 里面的 ops 操作对象列表的所有操作
   * @param {*} op 接收一个 TextOperation 实例对象
   */
  applyLocal(op) {
    this.doc = op.apply(this.doc); // 执行操作，将操作后的结果更新到 doc 中
    // 需要在本地缓存当前的本地更新
    this.buffer.push({ version: this.version, op }); // 将当前的操作对象推入到 buffer 中
    console.log(
      `客户端 ${this.name} 执行了本地操作，当前的文档内容为：${this.doc}`
    );
  }

  /**
   * 接收远程服务器传递过来的操作对象
   * @param {*} remoteOp 包含操作对象（来自于服务器）以及版本号的一个对象
   * {version: 1, op: TextOperation的实例对象}
   */
  receiveRemote(remoteOp) {
    if (this.buffer.length === 0) {
      // 进入此分支，说明本地是没有更新提交的，直接应用远程的操作对象
      this.doc = remoteOp.op.apply(this.doc);
      console.log(
        `客户端 ${this.name} 执行了远程操作，合并之后当前的文档内容为：${this.doc}`
      );
    } else {
      // 说明本地和远程都有更新，需要进行转换
      // 举个例子：假设你正在编辑一个文档，文档的内容为 abc
      // 你在文档末尾添加了一个 x，你相当于是 retain(3) + insert('x')，当前是一个本地操作
      // 你的本地操作还没有提交给服务器，这个时候，服务器推送过来了一个远程操作对象，这个远程对象假设是删除了 b 这个字符
      // 那么你现在就不能再基于 abc 进行操作了，因为服务器已经删除了 b 这个字符
      // 因此这里就需要将本地操作对象进行一个转换
      const [remotePrime, localPrime] = transform(
        remoteOp.op, // 别人（服务器）发过来的操作对象
        this.buffer[0].op // 你本地还没有提交的操作对象
      ); // 将远程操作对象和本地操作对象进行转换

      // remotePrime：转换后的远程操作对象，可以安全的应用到当前的文档内容上
      // localPrime：转换后的本地操作对象，已经调整好了位置
      // 可以继续应用到 remotePrime 执行之后的文档上，也可以安全的提交给服务器

      this.doc = remotePrime.apply(this.doc); // 执行转换后的远程操作对象
      // 然后本地缓存的操作对象也需要进行更新
      this.buffer[0].op = localPrime; // 将转换后的本地操作对象更新到 buffer 中

      console.log(
        `客户端 ${this.name} 执行了远程操作，合并之后当前的文档内容为：${this.doc}`
      );
    }

    this.version++; // 更新版本号
  }
}

module.exports = {
  Client,
};
